from django.core.validators import ValidationError
from django.utils.translation import gettext_lazy as _ 
from django.core.exceptions import ObjectDoesNotExist


def str2date(date_string):
    """
    Converts '%Y-%m-%d' formated string to date 
    """
    from datetime import date
    year, month, day = map(int, date_string.split('-')) #IGNORE:W0141
    return date(year, month, day)


def str2datetime(dt_field, all_data):
    """
    Converts given datetime field from string to datetime
    
    Used in oldforms 
    """
    from datetime import datetime
    from time import strptime
    dt_string = all_data[dt_field+'_date'] + ' ' + all_data[dt_field+'_time']
    try:
        dt = datetime(*strptime(dt_string, '%Y-%m-%d %H:%M:%S')[:6]) #IGNORE:W0142
    except ValueError:
        dt = datetime(*strptime(dt_string, '%Y-%m-%d %H:%M')[:5]) #IGNORE:W0142
    return dt


def isLocationFree(field_data, all_data):
    """
    Validator that tests if there are additional concerts held on the given
    location at the given time period
    """
    try:
        loc_id = int(field_data)
        concert_title = all_data['title'] # I don't get an id here, but concert_title is unique
        start_time = str2datetime('start_time', all_data)
        end_time = str2datetime('end_time', all_data)
        from models import Concert #IGNORE:W0403
        from django.db.models.query import Q
        # We'll get a false positive if only the title is changed. Can't do better - Django isn't passing objects ID  
        time_conflicts = Concert.objects.filter( #IGNORE:E1101
            Q(location=loc_id)
        ).exclude(
            Q(title=concert_title) | 
            Q(end_time__lt=start_time) |
            Q(start_time__gt=end_time) 
        ) # Exclude this concert
        if time_conflicts.count() > 0:
            raise ValidationError, _('The selected location is already reserved at the given time.')
    except (ValueError, ObjectDoesNotExist): #IGNORE:W0704
        pass # Location wasn't selected - other field will raise an Exception    


def isBellowLocationTicketCapacity(field_data, all_data):
    """
    Validator that tests if the number of old tickets is less than locations
    ticket capacity
    """
    try:
        loc_id = int(all_data['location'])
        sold_tickets = int(field_data)
        from models import Location #IGNORE:W0403
        ticket_capacity = Location.objects.get(pk=loc_id).ticket_capacity #IGNORE:E1101
        if ticket_capacity < sold_tickets:
            raise ValidationError, _(
                "Must be bellow maximum capacity (%(capacity)d) for the selected location."
            ) % {
                'capacity': ticket_capacity
            }
    except (ValueError, ObjectDoesNotExist): #IGNORE:W0704
        pass # Location wasn't selected - other field will raise an Exception    


class IsNotBeforeOtherDate(object):
    """
    Validator that tests if a given date is not before the other date
    """
    def __init__(self, other_field_name, error_message):
        self.other, self.error_message = other_field_name, error_message

    def __call__(self, field_data, all_data):
        if str2date(field_data) < str2date(all_data[self.other]):
            raise ValidationError, self.error_message


class IsAfterOtherDateTime(object):
    """
    Validator that tests if datetime(stop_field) > datetime(start_field) 
    """
    def __init__(self, stop_field, start_field, error_message):
        self.stop_field, self.start_field = stop_field, start_field
        self.error_message = error_message

    def __call__(self, field_data, all_data): #IGNORE:W0613
        # Validator will be called twice, so wait for the second time to
        # proccess, or we'll get 2 same errors - we know time has ':' in it
        if ':' in field_data: # Yeah, it's a hack :(
            if str2datetime(self.stop_field, all_data) <= \
            str2datetime(self.start_field, all_data):
                raise ValidationError, self.error_message


def isValidConcertTypeForFestival(concert_type, fest_id):
    """
    Function that tests if a concert type is valid for the selected festival
    
    If festival type is 'Competition' ('C') it must not be 'Invalid' ('I'),
    otherwise it must be 'Invalid' ('I')
    """
    from models import Festival #IGNORE:W0403
    try:
        f = Festival.objects.get(pk=fest_id) #IGNORE:E1101
        if  (f.fest_type == 'C' and concert_type != 'I') or \
            (f.fest_type != 'C' and concert_type == 'I'):
            return True
    except ObjectDoesNotExist: #IGNORE:W0704
        pass # Fail silently
    return False


class IsValidConcertType(object):
    def __call__(self, field_data, all_data):
        try:
            fest_id = int(all_data['festival'])
            if not isValidConcertTypeForFestival(field_data, fest_id):
                raise ValidationError, _('Invalid choice selected.')
        except ValueError: #IGNORE:W0704
            pass # Festival wasn't selected - other field will raise an Exception